﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Reflection;
using System.ServiceModel;
using System.ServiceModel.Channels;
using System.ServiceModel.Web;
using System.Xml;

using System.Threading;

using WebDavProcessor;
using WebDavProcessor.Interfaces;

using WCFServer.Helpers;
using WCFServer.Collections;

namespace WCFServer
{
    [ServiceBehavior(InstanceContextMode = InstanceContextMode.Single, ConcurrencyMode = ConcurrencyMode.Multiple)]
    class WebDAVServer:IWebDAVServer, IPeerNode
    {
        WebDavProcessor.WebDavProcessor webDavProcessor;
        string address;
        IPeerNode peerChanel;
        TimeDependedDictionary<string, string> addressDictionary;
        int timeout;
        DataProviders.DataAdapter dataProvider;

        public WebDAVServer()
        {
            try
            {
                XmlDocument doc = LoadConfig();
                XmlNode node = doc.SelectSingleNode("configuration/system.serviceModel/services/service/host/baseAddresses/add");
                address = node.Attributes["baseAddress"].InnerText;

                int curTime = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["referenceCurrentTime"]);
                int liveTime = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["referenceLiveTime"]);
                addressDictionary = new TimeDependedDictionary<string, string>(liveTime, curTime);

                //dataProvider = new DataProviders.FileSystemDataAdapter(System.Configuration.ConfigurationManager.ConnectionStrings[1].ConnectionString);
                dataProvider = new DataProviders.XmlFSDataAdapter(System.Configuration.ConfigurationManager.ConnectionStrings[1].ConnectionString, System.Configuration.ConfigurationManager.ConnectionStrings[2].ConnectionString);
                webDavProcessor = new WebDavProcessor.WebDavProcessor(dataProvider);

                Console.Write("WebDav service starts at {0}\nwait...", address);

                ChannelFactory<IPeerNode> channelFactory = new ChannelFactory<IPeerNode>("PeerClientEndpoint");
                channelFactory.Open();
                peerChanel = channelFactory.CreateChannel();


                Console.WriteLine();
                peerChanel.InvalidateNode(string.Empty);

                timeout = Convert.ToInt32(System.Configuration.ConfigurationManager.AppSettings["timeout"]);
            }
            catch(Exception e)
            {
                Console.WriteLine(e.Message);
            }
        }

        
        

        public Stream WebDAVRequest(Stream str, string filename, HttpRequestMessageProperty request)
        {

            try
            {
                WriteInfoString2Console(str, filename, request);

                //-----------------------------
                // проверка на редирект

                // если ссылка на этот ресурс очень новая, то заново запрашивать и ждать таймаут необязательно
                if (!addressDictionary.IsCurrent(filename))
                {
                    peerChanel.RequireResource(address, filename);

                    Thread.Sleep(timeout);               

                }                

                //-----------------------------
                if (!addressDictionary.ContainsKey(filename) && request.Method == "PUT")
                    addressDictionary.Add(filename, address);
                
                string rAddress;
                if (addressDictionary.ContainsKey(filename))
                {
                    rAddress = addressDictionary[filename];
                    if (rAddress == address)
                    {
                        //ресурс на этом узле
                        IWebDavRequest webDavRequest = request.ToWebDavRequest(filename, str);
                        IWebDavResponse response = webDavProcessor.ProcessRequest(webDavRequest);

                        //WebOperationContext.Current.OutgoingResponse.StatusCode = response.StatusCode;
                        foreach (string key in response.Headers.Keys)
                            WebOperationContext.Current.OutgoingResponse.Headers.Add(key, response.Headers[key]);
                        
                        return response.OutputStream;
                    }
                    else
                    {
                        //редирект на узел с ресурсом
                        WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.Redirect;
                        WebOperationContext.Current.OutgoingResponse.Location = Path.Combine(rAddress+"/", filename);
                    }
                }
                else
                {
                    //ошибка 404 not found
                    WebOperationContext.Current.OutgoingResponse.StatusCode = System.Net.HttpStatusCode.NotFound;
                }
            }
            catch (Exception exc)
            {
                Console.WriteLine(exc.Message);
            }
            return null;
        }

        private void WriteInfoString2Console(Stream str, string filename, HttpRequestMessageProperty request)
        {
            Console.WriteLine("----------------------------------------------------------------");
            Console.WriteLine(DateTime.Now.ToString());
            Console.WriteLine(string.Format("new request: {0}", filename));
            //Console.WriteLine(OperationContext.Current.EndpointDispatcher.EndpointAddress.Uri.ToString());

            foreach (string header in request.Headers.Keys)
                Console.WriteLine(string.Format("{0}: {1}", header, request.Headers[header]));

            Console.WriteLine(string.Format("method: {0}", request.Method));

            if (str != null)
            {
                StreamReader sr = new StreamReader(str);
                string tmp = sr.ReadToEnd();
                Console.WriteLine("request body:");
                Console.WriteLine(tmp);
            }
            Console.WriteLine("----------------------------------------------------------------");
        }

        void IPeerNode.RequireResource(string addressRequirer, string resource)
        {
            Console.Write(string.Format("[{0}]: ", DateTime.Now.ToLongTimeString()));
            Console.WriteLine(string.Format("{0} requires {1}", addressRequirer, resource));
            
            //если ресурс найден на мне, то вернуть респонс
            //проверка наличия ресурса                
            if (dataProvider.IsExist(resource))
            {
                peerChanel.ResponseResource(address, addressRequirer, resource);
            }
        }
        void IPeerNode.ResponseResource(string addressResponser, string addressRequirer, string resource)
        {
            // сначала думал сделать только для запрашивающего узла
            if (address == addressRequirer)
            {
            }
            // но потом решил, что лучше у всех узлов, получивших сообщение, добавить автоматом
            addressDictionary.Add(resource, addressResponser);
        }
        void IPeerNode.InvalidateNode(string address)
        {
            if (!string.IsNullOrEmpty(address))
            {
                Console.Write(string.Format("[{0}]: ", DateTime.Now.ToLongTimeString()));
                Console.WriteLine(string.Format("{0} is started", address));
            }
        }

        public Stream PeerNode_Update()
        {
            Console.WriteLine(string.Format("{0} is running", address));
            peerChanel.InvalidateNode(address);
            return null;
        }

        private static XmlDocument LoadConfig()
        {
            try
            {
                XmlDocument doc = new XmlDocument();
                doc.Load(string.Format("{0}{1}", Assembly.GetExecutingAssembly().Location, ".config"));
                return doc;
            }
            catch (System.IO.FileNotFoundException e)
            {
                throw new Exception("No configuration file found.", e);
            }
        }

        public Stream WebDAVRequest_GET(string filename)
        {
            return WebDAVRequest(null, filename, OperationContext.Current.IncomingMessageProperties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty);
        }
        public Stream WebDAVRequest_DELETE(string filename)
        {
            return WebDAVRequest(null, filename, OperationContext.Current.IncomingMessageProperties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty);
        }
        public Stream WebDAVRequest_PUT(Stream data, string filename)
        {
            MemoryStream ms = new MemoryStream();
            data.CopyTo(ms);
            return WebDAVRequest(ms, filename, OperationContext.Current.IncomingMessageProperties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty);
        }

        public Stream WebDAVRequest_PROPFIND(Stream data, string filename)
        {
            MemoryStream ms = new MemoryStream();
            data.CopyTo(ms);
            return WebDAVRequest(ms, filename, OperationContext.Current.IncomingMessageProperties[HttpRequestMessageProperty.Name] as HttpRequestMessageProperty);
        }

    }
}
